﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Media;

namespace DateRangeSlider
{
  public static class RulerUtils
  {
    private static readonly long DaySpan = TimeSpan.FromDays(1).Ticks;
    private static readonly long WeekSpan = 7 * DaySpan;
    private static readonly long MonthSpan = 30 * DaySpan;
    private static readonly long QuarterSpan = 3 * MonthSpan;
    private static readonly long YearSpan = 365 * DaySpan;
    private static readonly long DecadeSpan = 10 * YearSpan;

    private static Date GetCenter(DateTime l, DateTime r)
    {
      if (l.Year / 10 < r.Year / 10) return new Decade(r);
      if (l.Year < r.Year) return new Year(r);
      if (l.Month / 3 < r.Month / 3) return new Quarter(r);
      if (l.Month < r.Month) return new Month(r);
      if (r - l >= TimeSpan.FromDays(7) || l.DayOfWeek < r.DayOfWeek) return new Week(r);
      return new Day(r);
    }
//    private static Step GetStep(DateTime l, DateTime r)
//    {
//      var span = (r - l).Ticks;
//      if (span >= DecadeSpan) return Step.Decade;
//      if (span >= YearSpan) return Step.Year;
//      if (span >= QuarterSpan) return Step.Quarter;
//      if (span >= MonthSpan) return Step.Month;
//      if (span >= WeekSpan) return Step.Week;
//      return Step.Day;
//    }
    private static Step GetStep(Date center)
    {
      if (center is Decade) return Step.Decade;
      if (center is Year) return Step.Year;
      if (center is Quarter) return Step.Quarter;
      if (center is Month) return Step.Month;
      if (center is Week) return Step.Week;
      if (center is Day) return Step.Day;
      throw new NotSupportedException();
    }
    private static double Measure(Func<string, FormattedText> format, IEnumerable<Date> dates)
    {
      return dates.Sum(d => format(d.ToString()).Width + 30);
    }
    public static IEnumerable<Date> Ticks(DateTime l, DateTime r, double widthBudget, Func<string, FormattedText> format)
    {
      var center = GetCenter(l, r);
      return LessToMoreDetails(GetStep(center))
        .Select(level => CalcRuler(l, r, center, level.Item1, level.Item2))
        .LastOrDefault(dates => Measure(format, dates) < widthBudget)        
        ?? Enumerable.Empty<Date>();
    }

    private static readonly Tuple<Step, Verbosity>[] Details =
      new[]
        {
          Tuple.Create(Step.Decade, Verbosity.Long),
          Tuple.Create(Step.Year, Verbosity.Long),
          Tuple.Create(Step.Quarter, Verbosity.Short),
          Tuple.Create(Step.Quarter, Verbosity.Middle),
          Tuple.Create(Step.Quarter, Verbosity.Long),
          Tuple.Create(Step.Month, Verbosity.Short),
          Tuple.Create(Step.Month, Verbosity.Middle),
          Tuple.Create(Step.Month, Verbosity.Long),
          Tuple.Create(Step.Week, Verbosity.Short),
          Tuple.Create(Step.Week, Verbosity.Long),
          Tuple.Create(Step.Day, Verbosity.Short),
          Tuple.Create(Step.Day, Verbosity.Long),
        };
    public static IEnumerable<Tuple<Step, Verbosity>> LessToMoreDetails(Step start)
    {
      return Details.Where(d => d.Item1 <= start);
    }

    private static IEnumerable<Date> CalcRuler(DateTime l, DateTime r, Date center, Step step, Verbosity verbosity)
    {
      for (var i = center; i >= l; i = i.Prev(step))
      {
        i.Verbosity = verbosity;
        yield return i;
      }
      for (var i = center.Next(step); i <= r; i = i.Next(step))
      {
        i.Verbosity = verbosity;
        yield return i;
      }
    }
  }
  public enum Verbosity { Short, Middle, Long }
  public enum Step { Day, Week, Month, Quarter, Year, Decade }
  public abstract class Date
  {
    public Verbosity Verbosity { get; set; }
    public DateTime Data { get; private set; }

    protected Date(DateTime date)
    {
      Data = date;
    }

    public static bool operator >=(Date x, DateTime y)
    {
      return x.Data >= y;
    }
    public static bool operator <=(Date x, DateTime y)
    {
      return x.Data <= y;
    }

    public Date Prev(Step step)
    {
      switch (step)
      {
        case Step.Day: return new Day(Data - TimeSpan.FromDays(1));
        case Step.Week: return new Week(Data - TimeSpan.FromDays(7));
        case Step.Month: return new Month(Data.Month == 1
          ? new DateTime(Data.Year - 1, 12, 1)
          : new DateTime(Data.Year, Data.Month - 1, 1));
        case Step.Quarter: return new Month(Data.Month <= 3
          ? new DateTime(Data.Year - 1, Data.Month + 9, 1)
          : new DateTime(Data.Year, Data.Month - 3, 1));
        case Step.Year: return new Month(new DateTime(Data.Year - 1, 1, 1));
        case Step.Decade: return new Month(new DateTime(Data.Year - 10, 1, 1));
      }
      throw new NotSupportedException();
    }
    public Date Next(Step step)
    {
      switch (step)
      {
        case Step.Day: return new Day(Data + TimeSpan.FromDays(1));
        case Step.Week: return new Week(Data + TimeSpan.FromDays(7));
        case Step.Month: return new Month(Data.Month == 12
          ? new DateTime(Data.Year + 1, 1, 1)
          : new DateTime(Data.Year, Data.Month + 1, 1));
        case Step.Quarter: return new Month(Data.Month >= 10
          ? new DateTime(Data.Year + 1, Data.Month - 9, 1)
          : new DateTime(Data.Year, Data.Month + 3, 1));
        case Step.Year: return new Month(new DateTime(Data.Year + 1, 1, 1));
        case Step.Decade: return new Month(new DateTime(Data.Year + 10, 1, 1));
      }
      throw new NotSupportedException();
    }
  }
  public class Decade : Date
  {
    public Decade(DateTime date)
      : base(new DateTime((date.Year / 10) * 10, 1, 1))
    {
    }

    public override string ToString()
    {
      return Data.Year.ToString();
    }
  }
  public class Year : Date
  {
    public Year(DateTime date)
      : base(new DateTime(date.Year, 1, 1))
    {
    }

    public override string ToString()
    {
      return Data.Year.ToString();
    }
  }
  public class Quarter : Date
  {
    public Quarter(DateTime date)
      : base(new DateTime(date.Year, ((date.Month-1) / 3) * 3+1, 1))
    {
    }

    public override string ToString()
    {
      switch (Verbosity)
      {
        case Verbosity.Long: return Data.ToString("MMMM, yyyy");
        case Verbosity.Middle: return Data.ToString("MMM yyyy");
        case Verbosity.Short: return Data.ToString("MMM`yy");
      }
      return Data.ToString("Y");
    }
  }

  public class Month : Date
  {
    public Month(DateTime date)
      : base(new DateTime(date.Year, date.Month, 1))
    {
    }

    public override string ToString()
    {
      switch (Verbosity)
      {
        case Verbosity.Long: return Data.ToString("MMMM, yyyy");
        case Verbosity.Middle: return Data.ToString("MMM yyyy");
        case Verbosity.Short: return Data.ToString("MMM`yy");
      }
      return Data.ToString("Y");
    }
  }


  public class Week : Date
  {
    public Week(DateTime date)
      : base(date - TimeSpan.FromDays((int)date.DayOfWeek))
    {
    }

    public override string ToString()
    {
      switch (Verbosity)
      {
        case Verbosity.Long: return Data.ToString("MMMM d");
        case Verbosity.Short: return Data.ToString("MMM d");
      }
      return Data.ToString("M");
    }
  }
  public class Day : Date
  {
    public Day(DateTime date)
      : base(date.Date)
    {
    }

    public override string ToString()
    {
      switch (Verbosity)
      {
        case Verbosity.Long: return Data.ToString("MMMM d");
        case Verbosity.Short: return Data.ToString("MMM d");
      }
      return Data.ToString("M");
    }
  }
}